<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        //关于闭包引入
        //案例一 counter 为全局变量 不安全
        var counter = 0;
        function add() {
            return counter += 1;
        }
        add();
        add();
        add();//3

        //案例二 局部变量 隐藏了变量 counter 但未完成预期功能
        function add() {
            var counter = 0;
            return counter += 1;
        }
        add();
        add();
        add();//1

        //案例三 闭包 实现了变量的隐藏 也完成了相应的功能
        var add = (function () {
            var counter = 0;
            return function () {return counter += 1;}
        })();
        add();
        add();
        add();//3        
        // 通过add引用立即执行表达式里面的返回值(函数)function () {return counter += 1;} 此函数不释放
        // 使得counter常驻内存，不释放
        // 从而实现计add函数的调用次数/ 实现add调用一次counter+1;
        // 但是直接访问counter，访问不到(匿名函数立即执行表达式里面的所以counter是局部变量，直接访问不到，增加了安全性)


        //闭包（closure）
        // - 函数与对其词法环境的引用共同构成闭包。
        // - 闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所需访问的所有局部变量!!!
        // 案例3中闭包就是  var counter = 0; + function () {return counter += 1;}

        // 闭包的作用
        // - 可通过闭包访问函数作用域内的局部变量
        // - 使函数中的变量被保存在内存中不被释放
        // 如手机中的弹框：最好就是只实例化一个，让其中内容改变就好(单例模式)(就需要常驻内存)
        // 或者可以理解为拍电影的导演，整个生命周期中只有一个贯穿就好

        // 闭包的缺点
        // - 由于闭包会使得函数中的变量都被保存在内存中，内存消耗大
        // - 闭包会在父函数外部，改变父函数内部变量的值(小风险)

        //闭包的常见形式—— 以普通函数形式返回
        //案例1
        var outer = function foo() {
            var b = "local";
            
            function inner() {
                return b;
            }
            
            return inner;
        }();
        console.log(outer());

        //案例2
        function foo() {
            var b = "local";
            
            function inner() {
                return b;
            }
            
            return inner;
        }
        var outer = foo();
        console.log(outer());

        //案例3
        var outer;

        function foo() {
            var b = "local";

            function inner() {
            return b;
            }

            outer = inner;
        }
        foo();
        console.log(outer()); //"local"

        // 例题
        function foo(x) {
            var tmp = 3;
            return function(y) {
                console.log(x + y + ++tmp);
            };
        }
        var fee = foo(2);
        fee(10); //16
        fee(10); //17
        fee(10); //18
        // 闭包： x + var tmp = 3; + function(y) {console.log(x + y + ++tmp);};
        
        var tmp = 5;
        function foo(x) {
            var tmp = 3;
            return function(y) {
                console.log(x + y + ++tmp);
            };
        }
        var fee = foo(2);
        fee(10); //16
        fee(10); //17
        fee(10); //18
        // 不变，因为var tmp = 3; 将var tmp = 5;屏蔽掉了
        // 但是var tmp = 5; 也在这个包里 
        // 只要能访问到就在,一直到全局里
        // var tmp = 5;不写也没事因为屏蔽掉了

        // 验证:
        var tmp = 3;
        function foo(x) {
            return function(y) {
                console.log(x + y + ++tmp);
            };
        }
        var fee = foo(2);
        fee(10); //16
        fee(10); //17
        fee(10); //18

        //闭包常见形式——作为对象的方法返回
        function counter() {
            var n = 0;
            return {
                count: function() {
                    return ++n;
                },
                reset: function() {
                    n = 0;
                    return n;
                }
            };
        }
        var c = counter();
        var d = counter();  
        console.log(c.count()); //1
        console.log(d.count()); //1
        console.log(c.reset()); //0
        console.log(c.count()); //1
        console.log(d.count()); //2
        // c,d是两个不同的对象即有两对方法互不影响
        
        // c = counter(); 创建一个执行上下文对象,里面有一个n
        // 所以c/d中的两个方法访问的是同样的一个n
        // d = counter(); 创建一个新的执行上下文对象,一个新的n

        // 实现单例模式
        // 比如说我现在的需求是这样的，在网页中有时候会需要遮罩层，调用的时候我就创建一个，
        // 但是你不可能每次调用创建，所以如果存在就用以前的，如果不存在就创建新的
        function fn() {
            var a;
            return function() {
                return a || (a = document.body.appendChild(document.createElement('div')));
            }
        };
        var f = fn();
        f();
    </script>
</body>
</html>